﻿//#define zip
/*************************************************
Zip文件压缩包
author：Daixiwei
**************************************************/
#if zip
using ICSharpCode.SharpZipLib.GZip;
#endif
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

namespace taurus.client
{
    
    public class FilePack20 : IFilePack
    {
        private const int VER = 5;
        private string _dirDir;
        private string _rootDir;
        private int _totalFile;
        private int _currenFile;
        private string _packfile;
        private PackMode _mode = PackMode.Read;

        private Stream _stream;
        private List<string> _fileList;
        private Dictionary<string, FileHead> _headMap;

        private bool executing = false;
        private object _lock_obj = new object();
        
        public FilePack20(string packfile, PackMode mode)
        {
            _headMap = new Dictionary<string, FileHead>();
            _packfile = packfile;
            _mode = mode;
            _rootDir = string.Empty;
        }
        
        void Init(string dirPath,string filter, List<string> fileList)
        {
            _dirDir = dirPath;
            _dirDir = _dirDir.Replace("\\", "/");
            char last = _dirDir[_dirDir.Length - 1];
            if (last != '/')
            {
                _dirDir += "/";
            }
            if (!Directory.Exists(_dirDir))
                new DirectoryInfo(_dirDir).Create();
            if (_mode == PackMode.Write)
            {
                if (fileList == null)
                {
                    _fileList = new List<string>();
                    var _dir = new DirectoryInfo(_dirDir);
                    FilesCount(_fileList, filter, _dir);
                }else 
                {
                    _fileList = fileList;
                }
                _totalFile = _fileList.Count;
            }
        }

        #region byte stream
        /// <summary>
        /// Write an unsigned short in little endian byte order.
        /// </summary>
        private void WriteLeShort(int value)
        {
            unchecked
            {
                _stream.WriteByte((byte)(value & 0xff));
                _stream.WriteByte((byte)((value >> 8) & 0xff));
            }
        }

        /// <summary>
        /// Write an int in little endian byte order.
        /// </summary>
        private void WriteLeInt(int value)
        {
            unchecked
            {
                WriteLeShort(value);
                WriteLeShort(value >> 16);
            }
        }

        /// <summary>
        /// Write an int in little endian byte order.
        /// </summary>
        private void WriteLeLong(long value)
        {
            unchecked
            {
                WriteLeInt((int)value);
                WriteLeInt((int)(value >> 32));
            }
        }

        private byte[] buf1 = new byte[1];

        /// <summary>
		/// Read a <see cref="byte"/> from the input stream.
		/// </summary>
		/// <returns>Returns the byte read.</returns>
		private int ReadLeByte()
        {
           
            if (_stream.Read(buf1, 0, 1) == -1)
                return (-1);
            return (buf1[0] & 0xFF);
        }

        /// <summary>
		/// Read an <see cref="short"/> in little endian byte order.
		/// </summary>
		/// <returns>The short value read case to an int.</returns>
		private int ReadLeShort()
        {
            return ReadLeByte() | (ReadLeByte() << 8);
        }

        /// <summary>
        /// Read an <see cref="int"/> in little endian byte order.
        /// </summary>
        /// <returns>The int value read.</returns>
        private int ReadLeInt()
        {
            return ReadLeShort() | (ReadLeShort() << 16);
        }

        /// <summary>
        /// Read a <see cref="long"/> in little endian byte order.
        /// </summary>
        /// <returns>The long value read.</returns>
        public long ReadLeLong()
        {
            return (uint)ReadLeInt() | ((long)ReadLeInt() << 32);
        }
        #endregion

        /// <summary>
        /// 打包文件
        /// </summary>
        /// <param name="dirPath">文件所在目录</param>
        public void PackFile(string dirPath)
        {
            if (_mode == PackMode.Read) throw new Exception("pack mode is read!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath,null, null);
            AddPackEntrySyn();
        }

        /// <summary>
        /// 打包文件
        /// </summary>
        /// <param name="dirPath">文件所在目录</param>
        /// <param name="filter">列如".jpg|.png"过滤jpg和png文件</param>
        public void PackFile(string dirPath,string filter)
        {
            if (_mode == PackMode.Read) throw new Exception("pack mode is read!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath, filter, null);
            AddPackEntrySyn();
        }


        /// <summary>
        /// 打包文件
        /// </summary>
        /// <param name="dirPath">文件所在目录</param>
        /// <param name="fileList">文件列表</param>
        public void PackFile(string dirPath, List<string> fileList)
        {
            if (_mode == PackMode.Read) throw new Exception("pack mode is read!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath, null, fileList);
            AddPackEntrySyn();
        }

        /// <summary>
        /// 异步打包文件
        /// </summary>
        /// <param name="dirPath">文件所在目录</param>
        public void PackFileSyn(string dirPath)
        {
            if (_mode == PackMode.Read) throw new Exception("pack mode is read!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath,null, null);
            new Thread(AddPackEntrySyn).Start();
        }

        /// <summary>
        /// 打包文件
        /// </summary>
        /// <param name="dirPath">文件所在目录</param>
        /// <param name="filter">列如".jpg|.png"过滤jpg和png文件</param>
        public void PackFileSyn(string dirPath, string filter)
        {
            if (_mode == PackMode.Read) throw new Exception("pack mode is read!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath, filter, null);
            new Thread(AddPackEntrySyn).Start();
        }

        /// <summary>
        /// 打包文件
        /// </summary>
        /// <param name="dirPath">文件所在目录</param>
        /// <param name="fileList">文件列表</param>
        public void PackFileSyn(string dirPath, List<string> fileList)
        {
            if (_mode == PackMode.Read) throw new Exception("pack mode is read!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath, null, fileList);
            new Thread(AddPackEntrySyn).Start();
        }

        private void AddPackEntrySyn()
        {
            try
            {
                var stream = File.Create(_packfile);
#if zip
                _stream = new GZipOutputStream(stream);
                ((GZipOutputStream)_stream).SetLevel(9);
#else
                _stream = stream;
#endif
                WriteLeShort(VER);
                WriteLeShort(_totalFile);

                List<FileHead> heads = new List<FileHead>();
                int offset = 4;
                foreach (string path in _fileList)
                {
                    var name = ShortFileName(path);
                    var head = new FileHead();
                    head.fileName = name;
                    head.path = path;
                    heads.Add(head);
                    var nameBytes = Encoding.UTF8.GetBytes(name);

                    if (nameBytes.Length > 0xFFFF)
                    {
                        throw new Exception("name too long.");
                    }
                    FileInfo fileInfo = new FileInfo(path);
                    head.len = (int)fileInfo.Length;
                    offset += 10 + nameBytes.Length;
                }

                foreach(FileHead head in heads)
                {
                    var nameBytes = Encoding.UTF8.GetBytes(head.fileName);
                    head.offset = offset;
                    WriteLeShort(nameBytes.Length);
                    _stream.Write(nameBytes, 0, nameBytes.Length);
                    WriteLeInt(head.offset);
                    WriteLeInt(head.len);
                    offset += head.len;
                }

                foreach (FileHead head in heads)
                {
                    byte[] buffer = File.ReadAllBytes(head.path);
                    _stream.Write(buffer, 0, buffer.Length);
                    _currenFile++;
                }
            }
            catch (Exception ex)
            {
                Logger.error(ex);
                Error = ex.Message;
            }
            finally
            {
                Close();
            }

            _fileList.Clear();
            GC.Collect();
            Complete = true;
            lock (_lock_obj)
            {
                executing = false;
            }
        }

        private static void FilesCount(List<string> _fileList, string filter, DirectoryInfo dirInfo)
        {
            FileInfo[] files = dirInfo.GetFiles();
            string[] filter_list = null;
            if(!string.IsNullOrEmpty(filter)) filter_list =filter.Split('|');
            if (files != null)
            {
                foreach (FileInfo fi in files)
                {
                    bool isFilter = false;
                    if (filter_list != null)
                    {
                        foreach(string f in filter_list)
                        {
                            if (f.Equals(fi.Extension))
                            {
                                isFilter = true;
                                break;
                            }
                        }
                    }
                    if (isFilter) continue;
                    _fileList.Add(fi.FullName);
                }
            }

            foreach (DirectoryInfo subdir in dirInfo.GetDirectories())
            {
                FilesCount(_fileList, filter, subdir);
            }
        }

        private string ShortFileName(string s, bool addRoot = true)
        {
            string a = s.Replace("\\", "/");
            string d = a.Replace(_dirDir, "");
            return addRoot && !string.IsNullOrEmpty(_rootDir) ? (_rootDir + "/" + d) : d;
        }

        /// <summary>
        /// 解压到指定文件夹
        /// </summary>
        /// <param name="dirPath">文件夹</param>
        public void UnPackFileSyn(string dirPath)
        {
            if (_mode == PackMode.Write) throw new Exception("pack mode is write!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath, null, null);
            new Thread(AddUnPackEntrySyn).Start();
        }

        /// <summary>
        /// 解压到指定文件夹
        /// </summary>
        /// <param name="dirPath">文件夹</param>
        public void UnPackFile(string dirPath)
        {
            if (_mode == PackMode.Write) throw new Exception("pack mode is write!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            Init(dirPath, null, null);
            AddUnPackEntrySyn();
        }

        private void AddUnPackEntrySyn()
        {
            try
            {
                ReadHeads();

                foreach (FileHead head in _headMap.Values)
                {
                    var cf = new FileInfo(head.path);
                    DirectoryInfo d = new DirectoryInfo(cf.DirectoryName);
                    if (!d.Exists) d.Create();
                    FileStream fs = File.Create(cf.FullName);
                    var buffer = GetFile(head);
                    fs.Write(buffer, 0, buffer.Length);
                    fs.Flush();
                    fs.Close();
                    _currenFile++;
                }
            }
            catch (Exception ex)
            {
                Logger.error(ex);
                Error = ex.Message;
            }
            finally
            {
                Close();
            }

            GC.Collect();
            Complete = true;
            lock (_lock_obj)
            {
                executing = false;
            }
        }

        private byte[] GetFile(FileHead head)
        {
            if (_stream == null) return null;
            _stream.Seek(head.offset, SeekOrigin.Begin);
            var buffer = new byte[head.len];
            _stream.Read(buffer, 0, buffer.Length);
            return buffer;
        }

        private void ReadHeads()
        {
            if (_stream != null) return;
            var stream = File.OpenRead(_packfile);
#if zip
            _stream = new GZipInputStream(stream);
#else
            _stream = stream;
#endif
            
            int ver = ReadLeShort();
            _totalFile = ReadLeShort();
            _headMap.Clear();
            for (int i = 0; i < _totalFile; ++i)
            {
                FileHead head = new FileHead();

                var len = ReadLeShort();
                var nameBytes = new byte[len];
                _stream.Read(nameBytes, 0, len);
                head.fileName = Encoding.UTF8.GetString(nameBytes);
                head.path = _dirDir + head.fileName;
                head.offset = ReadLeInt();
                head.len = ReadLeInt();
                _headMap.Add(head.fileName, head);
            }
        }

        /// <summary>
        /// 请先调用
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public byte[] GetFile(string fileName)
        {
            if (_mode == PackMode.Write) throw new Exception("pack mode is write!");
            lock (_lock_obj)
            {
                if (executing) throw new Exception("pack opt executing!");
                executing = true;
            }
            ReadHeads();
            lock (_lock_obj)
            {
                executing = false;
            }
            if (_headMap.ContainsKey(fileName))
            {
                FileHead head = _headMap[fileName];
                return GetFile(head);
            }
            return null;
        }

        /// <summary>
        /// 关闭流
        /// </summary>
        public void Close()
        {
            if (_stream != null)
            {
                _stream.Close();
                _stream = null;
            }
        }

        /// <summary>
        /// 当前进度
        /// </summary>
        public float Progress
        {
            get
            {
                if (_totalFile <= 0) return 0;
                return _currenFile / (float)_totalFile;
            }
        }

        /// <summary>
        /// 是否压缩或者解压完成
        /// </summary>
        public bool Complete { get; private set; }

        /// <summary>
        /// 打包错误描述
        /// </summary>
        public string Error { get; private set; }

        /// <summary>
        /// 根目录
        /// </summary>
        public string RootDir
        {
            set
            {
                if (string.IsNullOrEmpty(value)) return;
                _rootDir = value;
            }
        }
    }

    class FileHead
    {
        public string path;
        public string fileName;
        public int offset;
        public int len;
    }
}
